{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial: Building a Text Summarizer and Translator with LangChain\n",
    "\n",
    "## Overview\n",
    "\n",
    "This tutorial demonstrates how to create a language model application that summarizes text and translates the summary to Spanish using LangChain. The application uses a combination of custom functions, structured tools, and an agent to process input text efficiently.\n",
    "\n",
    "## Motivation\n",
    "\n",
    "In today's data-rich world, the ability to quickly summarize information and translate it into different languages is invaluable. This tutorial aims to show how to leverage language models and the LangChain framework to create a tool that can:\n",
    "\n",
    "1. Summarize lengthy text\n",
    "2. Translate the summary to Spanish\n",
    "3. Do both tasks in a single, streamlined process\n",
    "\n",
    "This type of tool can be useful for various applications, including content curation, multilingual communication, and rapid information processing.\n",
    "\n",
    "## Key Components\n",
    "\n",
    "1. **Custom Functions**: For summarization and translation\n",
    "2. **Structured Tools**: Wrappers for the custom functions\n",
    "3. **Prompt Template**: Instructions for the agent\n",
    "4. **Agent**: Orchestrates the use of tools based on the prompt\n",
    "5. **Agent Executor**: Runs the agent with specified parameters\n",
    "\n",
    "## Method Details\n",
    "\n",
    "### 1. Custom Functions\n",
    "\n",
    "Two main functions are defined:\n",
    "\n",
    "- A summarization function that takes input text and returns a summary\n",
    "- A translation function that takes input text and returns its Spanish translation\n",
    "\n",
    "Both functions use a PromptTemplate and a language model to perform their tasks.\n",
    "\n",
    "### 2. Structured Tools\n",
    "\n",
    "The custom functions are wrapped as StructuredTool objects. This allows the agent to use these functions as tools, providing a name, description, and input schema for each.\n",
    "\n",
    "### 3. Prompt Template\n",
    "\n",
    "A PromptTemplate is created with detailed instructions for the agent. It outlines the steps the agent should follow:\n",
    "1. Summarize the input text\n",
    "2. Translate the summary to Spanish\n",
    "3. Format the output with both the English summary and Spanish translation\n",
    "\n",
    "### 4. Agent and Agent Executor\n",
    "\n",
    "An agent is created using the tools and prompt. This agent is then wrapped in an AgentExecutor, which manages the execution of the agent. The executor is configured with parameters such as the maximum number of iterations and the early stopping method.\n",
    "\n",
    "### 5. Running the Agent\n",
    "\n",
    "A helper function is created to simplify running the agent. This function takes the agent executor and a query, runs the agent, and returns the output. The tutorial demonstrates this with a sample query about pangrams, showing how the entire pipeline works together to process the input text.\n",
    "\n",
    "## Conclusion\n",
    "\n",
    "This tutorial demonstrates how to create a powerful text processing tool using LangChain. By combining custom functions, structured tools, and an agent, we've created an application that can summarize text and translate the summary to Spanish in one seamless operation. This approach can be extended to include other languages or text processing tasks, making it a versatile foundation for various natural language processing applications.\n",
    "\n",
    "The strength of this approach lies in its modularity and flexibility. By using LangChain's components, we can easily modify or extend the functionality of our application. For instance, we could add more languages, implement different summarization techniques, or even incorporate other text processing tasks like sentiment analysis or keyword extraction."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Import necessary libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor, create_tool_calling_agent\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.prompts import PromptTemplate\n",
    "from langchain.tools import StructuredTool\n",
    "from pydantic import BaseModel, Field\n",
    "import os\n",
    "from dotenv import load_dotenv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load environment variables and initialize the language model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "load_dotenv()\n",
    "os.environ[\"OPENAI_API_KEY\"] = os.getenv('OPENAI_API_KEY')\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\", max_tokens=1000, temperature=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### first let's define the functions that the agent can use:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def summarize(text):\n",
    "    # Create a PromptTemplate for summarization\n",
    "    prompt = PromptTemplate(\n",
    "        input_variables=[\"text\"],  # Specify the input variable\n",
    "        template=\"Summarize the following text:\\n\\n{text}\\n\\nSummary:\"  # Define the template for summarization\n",
    "    )\n",
    "    chain = prompt | llm  # Create a chain by piping the prompt to the language model\n",
    "    return chain.invoke({\"text\": text}).content  # Invoke the chain with the input text and return the content of the response\n",
    "\n",
    "def translate(text):\n",
    "    # Create a PromptTemplate for translation\n",
    "    prompt = PromptTemplate(\n",
    "        input_variables=[\"text\"],  # Specify the input variable\n",
    "        template=\"Translate the following text to Spanish:\\n\\n{text}\\n\\nTranslation:\"  # Define the template for translation\n",
    "    )\n",
    "    chain = prompt | llm  # Create a chain by piping the prompt to the language model\n",
    "    return chain.invoke({\"text\": text}).content  # Invoke the chain with the input text and return the content of the response\n",
    "\n",
    "class TextInput(BaseModel):\n",
    "    # Define a Pydantic model for input validation\n",
    "    text: str = Field(description=\"The text to summarize or translate\")  # Define a text field with a description"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A fast brown fox leaps over a sluggish dog.\n",
      "La rápida zorra marrón salta sobre el perro perezoso.\n"
     ]
    }
   ],
   "source": [
    "# test the functions\n",
    "\n",
    "text = \"The quick brown fox jumps over the lazy dog.\"\n",
    "print(summarize(text))\n",
    "print(translate(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define the tools for the agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [\n",
    "    StructuredTool.from_function(\n",
    "        func=summarize,  # The function to be wrapped as a tool\n",
    "        name=\"Summarize\",  # Name of the tool\n",
    "        description=\"Useful for summarizing text\",  # Description of what the tool does\n",
    "        args_schema=TextInput  # The Pydantic model defining the input schema\n",
    "    ),\n",
    "    StructuredTool.from_function(\n",
    "        func=translate,  # The function to be wrapped as a tool\n",
    "        name=\"Translate\",  # Name of the tool\n",
    "        description=\"Useful for translating text to Spanish\",  # Description of what the tool does\n",
    "        args_schema=TextInput  # The Pydantic model defining the input schema\n",
    "    )\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Initialize the agent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"input\", \"agent_scratchpad\"],  # Define the input variables for the prompt\n",
    "    template=\"\"\"Summarize the following text and then translate the summary to Spanish:\n",
    "\n",
    "Text: {input}\n",
    "\n",
    "Use the following steps:\n",
    "1. Use the Summarize tool to summarize the text. Pass the entire text as the 'text' argument.\n",
    "2. Use the Translate tool to translate the summary to Spanish. Pass the summary as the 'text' argument.\n",
    "3. Immediately after using both tools, respond with the final result in the following format:\n",
    "   Summary (English): [English summary]\n",
    "   Translation (Spanish): [Spanish translation]\n",
    "\n",
    "Do not use any tools after providing the formatted output.\n",
    "\n",
    "{agent_scratchpad}\"\"\"  # Define the template for the agent's instructions\n",
    ")\n",
    "\n",
    "# Create an agent using the defined tools and prompt\n",
    "agent = create_tool_calling_agent(llm, tools, prompt)\n",
    "\n",
    "# Create an AgentExecutor to run the agent\n",
    "agent_executor = AgentExecutor(\n",
    "    agent=agent,  # The agent to execute\n",
    "    tools=tools,  # The tools available to the agent\n",
    "    verbose=True,  # Enable verbose output\n",
    "    max_iterations=3,  # Set maximum number of iterations\n",
    "    early_stopping_method=\"force\"  # Force stop after max_iterations\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_agent_with_query(agent_executor, query):\n",
    "    \"\"\"\n",
    "    Execute the agent with a given query and return the output.\n",
    "\n",
    "    Args:\n",
    "        agent_executor (AgentExecutor): The configured AgentExecutor to run.\n",
    "        query (str): The input text to be processed by the agent.\n",
    "\n",
    "    Returns:\n",
    "        str: The output generated by the agent after processing the query.\n",
    "    \"\"\"\n",
    "    # Invoke the agent_executor with the query as input\n",
    "    result = agent_executor.invoke({\"input\": query})\n",
    "    \n",
    "    # Extract and return the 'output' field from the result\n",
    "    return result['output']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  Example usage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
      "\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `Summarize` with `{'text': \"The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\"}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3mThe sentence \"The quick brown fox jumps over the lazy dog\" is a well-known pangram used in typography to showcase fonts, as it includes every letter of the English alphabet. Another, shorter pangram is \"Pack my box with five dozen liquor jugs,\" though it is less commonly used.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `Translate` with `{'text': \"The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\"}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[33;1m\u001b[1;3mLa rápida zorra marrón salta sobre el perro perezoso. Esta oración se utiliza a menudo como un pangrama en tipografía para mostrar ejemplos de fuentes, ya que contiene cada letra del alfabeto inglés. Sin embargo, no es el único pangrama que existe. Otro ejemplo es 'Empaca mi caja con cinco docenas de jarras de licor', que es más corto pero menos comúnmente utilizado.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `Summarize` with `{'text': \"The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\"}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[36;1m\u001b[1;3mThe sentence \"The quick brown fox jumps over the lazy dog\" is a well-known pangram used in typography to showcase fonts, as it includes every letter of the English alphabet. Another, shorter pangram is \"Pack my box with five dozen liquor jugs,\" though it is less commonly used.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
      "Invoking: `Translate` with `{'text': \"The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\"}`\n",
      "\n",
      "\n",
      "\u001b[0m\u001b[33;1m\u001b[1;3mLa rápida zorra marrón salta sobre el perro perezoso. Esta oración se utiliza a menudo como un pangrama en tipografía para mostrar ejemplos de fuentes, ya que contiene cada letra del alfabeto inglés. Sin embargo, no es el único pangrama que existe. Otro ejemplo es 'Empaca mi caja con cinco docenas de jarras de licor', que es más corto pero menos comúnmente utilizado.\u001b[0m\u001b[32;1m\u001b[1;3mSummary (English): The sentence \"The quick brown fox jumps over the lazy dog\" is a well-known pangram used in typography to showcase fonts, as it includes every letter of the English alphabet. Another, shorter pangram is \"Pack my box with five dozen liquor jugs,\" though it is less commonly used.\n",
      "\n",
      "Translation (Spanish): La rápida zorra marrón salta sobre el perro perezoso. Esta oración se utiliza a menudo como un pangrama en tipografía para mostrar ejemplos de fuentes, ya que contiene cada letra del alfabeto inglés. Sin embargo, no es el único pangrama que existe. Otro ejemplo es 'Empaca mi caja con cinco docenas de jarras de licor', que es más corto pero menos comúnmente utilizado.\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "\n",
      "Query:\n",
      "The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography \n",
      "to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram \n",
      "in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\n",
      "\n",
      "Result:\n",
      "Summary (English): The sentence \"The quick brown fox jumps over the lazy dog\" is a well-known pangram used in typography to showcase fonts, as it includes every letter of the English alphabet. Another, shorter pangram is \"Pack my box with five dozen liquor jugs,\" though it is less commonly used.\n",
      "\n",
      "Translation (Spanish): La rápida zorra marrón salta sobre el perro perezoso. Esta oración se utiliza a menudo como un pangrama en tipografía para mostrar ejemplos de fuentes, ya que contiene cada letra del alfabeto inglés. Sin embargo, no es el único pangrama que existe. Otro ejemplo es 'Empaca mi caja con cinco docenas de jarras de licor', que es más corto pero menos comúnmente utilizado.\n"
     ]
    }
   ],
   "source": [
    "# Define the input query\n",
    "query = \"\"\"The quick brown fox jumps over the lazy dog. This sentence is often used as a pangram in typography \n",
    "to display font examples, as it contains every letter of the English alphabet. However, it's not the only pangram \n",
    "in existence. Another example is 'Pack my box with five dozen liquor jugs', which is shorter but less commonly used.\"\"\"\n",
    "\n",
    "# Run the agent with the query\n",
    "result = run_agent_with_query(agent_executor, query)\n",
    "\n",
    "# Print the original query\n",
    "print(\"\\nQuery:\")\n",
    "print(query)\n",
    "\n",
    "# Print the result from the agent\n",
    "print(\"\\nResult:\")\n",
    "print(result)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
